/**
 * 北京钉图互动科技 all right reserver
 */
package com.sincebest.fivestar.common.exception.translate;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import com.sincebest.fivestar.common.ErrorCodeEntry;
import com.sincebest.fivestar.common.exception.BaseErrorCodes;

/**
 * 错误码映射
 * <p>
 * TODO 2016-07-09 待实现类的层级匹配，即定义了父类映射，使用子类也能获取对应的错误码
 * 
 * @author Wangyi
 * @since 2016年6月12日
 */
public class DefaultExceptionErrorCodeMapping implements ExceptionErrorCodeRegistar, ExceptionErrorCodeSupplier {

	private static final Logger log = LoggerFactory.getLogger(DefaultExceptionErrorCodeMapping.class);
	private static final String EXCEPTION_ERROR_CODE_MAPPING_FILENAME = "exception-error-mapping.properties";

	private final Map<String /* errorCode */, ErrorCodeEntry> errorCodeMap;
	private final Map<String /* exception class name */, String /* errorCode */> exceptionErrorCodeMap;
	private final Map<String /* exception class name */, ErrorCodeEntry> exceptionErrorCodeCache;

	public DefaultExceptionErrorCodeMapping() {
		this.errorCodeMap = new LinkedHashMap<String , ErrorCodeEntry>();
		this.exceptionErrorCodeMap = new LinkedHashMap<String, String>();
		this.exceptionErrorCodeCache = new LinkedHashMap<String, ErrorCodeEntry>();

		for (ErrorCodeEntry entry : BaseErrorCodes.BASE_ERROR_CODES) {
			errorCodeMap.put(entry.getCode(), entry);
		}
		if (log.isDebugEnabled()) {
			StringBuilder sb = new StringBuilder("---- 基本错误码 ----\n");
			for (ErrorCodeEntry entry : BaseErrorCodes.BASE_ERROR_CODES) {
				sb.append(entry).append("\n");
			}
			log.debug("\n{}", sb.toString());
		}
		tryLoadExceptionErrorCodeMappingFromConventionProperties();
	}

	void tryLoadExceptionErrorCodeMappingFromConventionProperties() {
		try {
			this.exceptionErrorCodeMap.putAll(this.loadExceptionErrorCodeMapping("classpath*:" + EXCEPTION_ERROR_CODE_MAPPING_FILENAME));
		} catch (IOException e) {
			log.error(e.getMessage());
		}
	}

	public Map<String, String> loadExceptionErrorCodeMapping(String locationPattern) throws IOException {
		Map<String, String> map = new HashMap<String, String>();
		ResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
		Resource[] reses = patternResolver.getResources(locationPattern);
		Properties props = new Properties();
		for (Resource res : reses) {
			try (InputStream isStream = res.getInputStream()) {
				try {
					log.debug("loading exception error code mapping: {}", res);
					props.load(isStream);
				} catch (Exception e) {
					log.error("load resource: {} error: {}, ignored", res, e.getMessage());
				}
			}
		}
		String key;
		for (Enumeration<Object> e = props.keys(); e.hasMoreElements();) {
			key = e.nextElement().toString();
			map.put(key, props.getProperty(key));
		}
		return map;
	}

	@Override
	public void registerException(Class<? extends Throwable> exceptionClass, ErrorCodeEntry errorCode) {
		exceptionErrorCodeCache.put(exceptionClass.getName(), errorCode);
	}

	@Override
	public void registerException(String exceptionClassName, ErrorCodeEntry errorCode) {
		exceptionErrorCodeCache.put(exceptionClassName, errorCode);
	}

	@Override
	public void registerErrorCode(Class<?> staticErrorCodeFieldClass) {
		this.registerErrorCode(ErrorCodeEntry.findStaticErrorCodes(staticErrorCodeFieldClass));
	}

	@Override
	public void registerErrorCode(ErrorCodeEntry errorCode) {
		if (errorCodeMap.containsKey(errorCode.getCode())) {
			log.warn("{}的错误码已存在，尝试注册的参数: {}", errorCode.getCode(), errorCode);
		}
		errorCodeMap.put(errorCode.getCode(), errorCode);
	}

	@Override
	public void registerErrorCode(Collection<ErrorCodeEntry> errorCodes) {
		if (errorCodes == null || errorCodes.isEmpty()) {
			return;
		}

		for (ErrorCodeEntry entry : errorCodes) {
			this.registerErrorCode(entry);
		}
	}

	@Override
	public ErrorCodeEntry getErrorCode(Class<? extends Throwable> exceptionClass) {
		return this.getErrorCode(exceptionClass.getCanonicalName());
	}

	@Override
	public ErrorCodeEntry getErrorCode(String exceptionClassName) {
		ErrorCodeEntry entry = exceptionErrorCodeCache.get(exceptionClassName);
		if (entry == null) {
			String errorCode = this.exceptionErrorCodeMap.get(exceptionClassName);
			if (errorCode != null) {
				entry = this.errorCodeMap.get(errorCode);
				if (entry != null) {
					exceptionErrorCodeCache.put(exceptionClassName, entry);
				}
			}
		}
		return entry != null ? entry : BaseErrorCodes.UNDEFINED;
	}

	// for unit test
	int getRegisterExceptionClassCount() {
		return this.exceptionErrorCodeMap.size();
	}

	// for unit test
	int getRegisterErrorCodeCount() {
		return this.errorCodeMap.size();
	}
}
